package org.liang.code.utils.pdf;

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.*;

import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SignMain {


    public static void main(String[] args) throws Exception {
//        addUnverifiedSignature(true);
//        addUnverifiedSignature(false);
        // extractVerifiedCryptoSignature();
        signPDF("C:/tmp/123.pdf");
    }

    /**
     * 添加未经验证的签名
     * @param visible 签名是否可见
     */
    private static void addUnverifiedSignature(boolean visible) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException {
        try {
            String visibility = visible ? "visible" : "invisible";
            String description = "Document with " + visibility + " signature";
            System.out.println(description);

            // 创建PDF
            Document document = new Document();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            PdfWriter writer = PdfWriter.getInstance(document, baos);
            document.open();
            // 设置PDF的作者
            writer.getInfo().put(PdfName.CREATOR, new PdfString(Document.getVersion()));

            // 添加自然段
            document.add(new Paragraph(description));
            // 关闭PDF
            document.close();

            // PDF 阅读器
            PdfReader reader = new PdfReader(baos.toByteArray());
            // 验证签名需要私钥和有效证书。请参阅此 JavaDoc
            // A verified signature would require a private key plus a valid certificate. see the JavaDoc of this
            // method for details
            // 创建一个签名
            PdfStamper stp = PdfStamper.createSignature(reader, baos, '\0', null, true);

            // 创建日历示例
            Calendar signDate = Calendar.getInstance();
            // 修改PDF的日期
            stp.setEnforcedModificationDate(signDate);

            // 获取签名实例。可以设置外观和其他参数
            PdfSignatureAppearance sap = stp.getSignatureAppearance();
            PdfDictionary dic = new PdfDictionary();
            // self signed
            // Filter=Adobe.PPKLite
            // M=yyyyMMdd
            dic.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
            dic.put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
            dic.put(PdfName.M, new PdfDate(signDate));
            // 设置签名词典
            sap.setCryptoDictionary(dic);
            // 设置签名日期
            sap.setSignDate(signDate);

            KeyStore ks = KeyStore.getInstance("pkcs12");

            ks.load(new FileInputStream("C:/tmp/Liang.pfx"), "liang1997".toCharArray());

            PrivateKey key = (PrivateKey) ks.getKey("1", "liang1997".toCharArray());
            Certificate[] chain = ks.getCertificateChain("1");

            sap.setCrypto(key, chain, null, PdfSignatureAppearance.SELF_SIGNED);
            // 签名原因
            sap.setReason("I'm the author");
            // 签名位置
            sap.setLocation("Lisbon");

            // 签名可见
            if (visible) {
                // 设置可见签名
                sap.setVisibleSignature(new Rectangle(300, 300), 1);
                // sap.setLayer2Text("Test signer");
                // 设置可见图像
                // sap.setImage(Image.getInstanceFromClasspath("image/demo.png"));
                // sap.setSignatureGraphic(Image.getInstanceFromClasspath("image/demo.png"));
            }

            // exclude the signature from the hash of the PDF and fill the resulting gap
            // 从 PDF 的哈希值中排除签名并填充生成的间隙
            Map<PdfName, Integer> exc = new HashMap<>();
            // Contents=10
            exc.put(PdfName.CONTENTS, 10);

            // 预关闭
            sap.preClose(exc);
            PdfDictionary update = new PdfDictionary();
            // Contents=aaa 十六进制写入
            update.put(PdfName.CONTENTS, new PdfString("aaaa").setHexWriting(true));
            // 真正关闭
            sap.close(update);

            String fileNamePrefix = visibility.substring(0, 1).toUpperCase() + visibility.substring(1);
            FileOutputStream fos = new FileOutputStream("C:/tmp/" + fileNamePrefix + "Signature.pdf");
            fos.write(baos.toByteArray());
            fos.close();

            InputStream resultIS = new ByteArrayInputStream(baos.toByteArray());
            // 读取数据PDF
            PdfReader resultReader = new PdfReader(resultIS);

            // 获取可读字段
            AcroFields fields = resultReader.getAcroFields();

            // 获取具有签名且已签名的字段名称
            List<String> signatures = fields.getSignedFieldNames();
            for (String signature : signatures) {
                // 打印数据
                printSignatureDetails(fields, signature);
            }
        } catch (DocumentException | IOException e) {
            System.err.println(e.getMessage());
        }
    }

    private static void extractVerifiedCryptoSignature() {

        System.out.println("Signature extraction");

        PdfPKCS7.loadCacertsKeyStore();

        try {
            InputStream is = SignMain.class.getResourceAsStream("/CryptoSignedSha256.pdf");
            PdfReader reader = new PdfReader(is);
            AcroFields fields = reader.getAcroFields();

            List<String> signatures = fields.getSignedFieldNames();
            for (String signature : signatures) {
                printSignatureDetails(fields, signature);

                PdfPKCS7 pk = fields.verifySignature(signature);

                X509Certificate certificate = pk.getSigningCertificate();
                PdfPKCS7.X509Name subjectFields = PdfPKCS7.getSubjectFields(certificate);
                System.out.println("Certificate subject fields: " + subjectFields);
                System.out.println("Certificate verified: " + pk.verify());

                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                System.out.println("Date signed: " + sdf.format(pk.getSignDate().getTime()));
                System.out.println("Timestamp verified: " + pk.verifyTimestampImprint());
            }
        } catch (SignatureException | IOException | NoSuchAlgorithmException e) {
            System.err.println(e.getMessage());
        }
    }

    private static void printSignatureDetails(AcroFields fields, String signature) {
        System.out.println("Signature: " + signature);
        System.out.println("Signature covers whole document: " + fields.signatureCoversWholeDocument(signature));
        System.out.println("Revision: " + fields.getRevision(signature));
    }

    private static void signPDF(String pdfPath) throws Exception {
        // PDF 阅读器
        PdfReader reader = new PdfReader(pdfPath);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 验证签名需要私钥和有效证书。请参阅此 JavaDoc
        // A verified signature would require a private key plus a valid certificate. see the JavaDoc of this
        // method for details
        // 创建一个签名
        PdfStamper stp = PdfStamper.createSignature(reader, baos, '\0', null, true);

        // 创建日历示例
        Calendar signDate = Calendar.getInstance();
        // 修改PDF的日期
        stp.setEnforcedModificationDate(signDate);

        // 获取签名实例。可以设置外观和其他参数
        PdfSignatureAppearance sap = stp.getSignatureAppearance();
        PdfDictionary dic = new PdfDictionary();
        // self signed
        // Filter=Adobe.PPKLite
        // M=yyyyMMdd
        dic.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
        dic.put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
        dic.put(PdfName.M, new PdfDate(signDate));
        // 设置签名词典
        sap.setCryptoDictionary(dic);
        // 设置签名日期
        sap.setSignDate(signDate);

        KeyStore ks = KeyStore.getInstance("pkcs12");

        ks.load(new FileInputStream("C:/tmp/Liang.pfx"), "liang1997".toCharArray());

        PrivateKey key = (PrivateKey) ks.getKey("1", "liang1997".toCharArray());
        Certificate[] chain = ks.getCertificateChain("1");

        sap.setCrypto(key, chain, null, PdfSignatureAppearance.SELF_SIGNED);
        // 签名原因
        sap.setReason("I'm the author");
        // 签名位置
        sap.setLocation("Lisbon");

        // 设置可见签名
        sap.setVisibleSignature(new Rectangle(300, 300), 1);

        // exclude the signature from the hash of the PDF and fill the resulting gap
        // 从 PDF 的哈希值中排除签名并填充生成的间隙
        Map<PdfName, Integer> exc = new HashMap<>();
        // Contents=10
        exc.put(PdfName.CONTENTS, 10);

        // 预关闭
        sap.preClose(exc);
        PdfDictionary update = new PdfDictionary();
        // Contents=aaa 十六进制写入
        update.put(PdfName.CONTENTS, new PdfString("aaaa").setHexWriting(true));
        // 真正关闭
        sap.close(update);

        FileOutputStream fos = new FileOutputStream("C:/tmp/456.pdf");
        fos.write(baos.toByteArray());
        fos.close();
    }
}
